Taking off from Pia's previous presentation on mapping using matplotlib last hack session, I tried to look for alternative ways to make maps in Python which are easier and more interactive.
After doing some research, it seems that good candidate for this is Bokeh, as can be seen in the Bokeh tutorials for interactive chloropleth maps.
After watching some PyCon 2015 videos, I can see that Bokeh is a promising data visualization tool for Python, since it's:
Looking into the Bokeh tutorial code, the data used for the chloropleth map is a built-in dataset for the Bokeh package, and has already been cleaned for use in mapping visualizations, as seen below:
In [ ]:
import bokeh
bokeh.sampledata.download()
In [ ]:
from bokeh.sampledata import us_counties
us_counties
The us_counties object has a .data attribute which outputs a dictionary. If we tidy the dict into a DataFrame, it can be easily seen that the the longitude and latitude coordinates are properly listed per county:
In [ ]:
import pandas as pd
counties = pd.DataFrame.from_dict(us_counties.data, orient='index')
counties.head()
In [ ]:
# getParts function: Return points for each shape object
# input: Shapefile
def getParts (shapeObj):
points = []
num_parts = len(shapeObj.parts)
end = len(shapeObj.points) - 1
segments = list(shapeObj.parts) + [end]
for i in range(num_parts):
points.append(shapeObj.points[segments[i]:segments[i+1]])
return points
In [ ]:
# getDict function: return a dict with the location's name, list of latitudes, and list of longitudes.
# input: muni name, shapefile, column number for muni IDs
def getDict (muni_name, shapefile, num=2):
muniDict = {muni_name: {} }
rec = []
shp = []
points = []
for i in shapefile.shapeRecords( ):
if i.record[num] == muni_name:
rec.append(i.record)
shp.append(i.shape)
for j in shp:
for i in getParts(j):
points.append(i)
lat = []
lng = []
for i in points:
lat.append( [j[0] for j in i] )
lng.append( [j[1] for j in i] )
muniDict[muni_name]['lat_list'] = lat
muniDict[muni_name]['lng_list'] = lng
return muniDict
In [ ]:
import numpy as np
import shapefile #from pyshp
from bokeh.plotting import figure, output_file, show
dat = shapefile.Reader("data\DSWD_PH_MC_Pop.shp") #read muni-level shapefile
munis = set([i[2] for i in dat.iterRecords()]) #get unique list of IDs
output_file("sample_map.html") #Bokeh output is an HTML file!
TOOLS="pan,wheel_zoom,box_zoom,reset,previewsave" #Bokeh built-in tools for interactive graphs
p = figure(title="Municipal-level Map", tools=TOOLS, plot_width=900, plot_height=800) #create Bokeh figure
for muni_name in munis: #plot data from shapefile
data = getDict(muni_name, dat)
p.patches(data[muni_name]['lat_list'], data[muni_name]['lng_list'], line_color="black")
show(p) #output Bokeh figure as sample_map.html
See how easy it was to make an interactive map in an HTML file using a few lines of code?
Now let's try to put the Bokeh interactive map inside the iPython notebook. Try playing with the map below using the interactive tools at the upper right corner of the map!
In [ ]:
from bokeh.io import output_notebook
output_notebook()
show(p)
Awesome! Now let's try to add some more data and functionality to the municipal-level map by exploring the dataset.
In [ ]:
## Data Cleaning and Processing
# extract names of municipalities from shapefile
muni_name = []
for i in dat.iterRecords():
if type(i[5])==bytes:
muni_name.append(i[5].decode("utf-8", "ignore"))
else:
muni_name.append(i[5])
muni_lati = []
muni_long = []
## extract lists of latitude and longitude values for each municipality from shapefile
muni_list = [i[4] for i in dat.iterRecords()] #get unique list of IDs
for muni_id in muni_list:
data = getDict(muni_id, dat, num=4)
muni_lati.append(data[muni_id]['lat_list'])
muni_long.append(data[muni_id]['lng_list'])
muni_lon = [i[0] for i in muni_long]
muni_lat = [i[0] for i in muni_lati]
col_names = ["REG_PSGC", "REG_NAME", "PROV_PSGC", "PROV_NAME", "MC_PSGC", "MC_NAME", "BRGYS", "POP_2010", "POP_GR", "PPOP_2011",\
"PPOP_2012", "PPOP_2013", "PPOP_2014", "POOR_2012", "NPOOR_2012"] #culled out from the XML shapefile metadata
#extract municipal-level data from shapefile
muni_data = [i for i in dat.iterRecords()]
muni_dat = pd.DataFrame(muni_data, columns=col_names)
In [156]:
#make two new columns:
# (1) muni_estpop: estimated 2012 population (by adding POOR_2012 and NPOOR_2012)
# (2) muni_pov: estimated 2012 poverty rate in percent (by dividing POOR_2012 with muni_estpop times 100)
muni_dat['muni_estpop'] = muni_dat["POOR_2012"]+muni_dat["NPOOR_2012"]
muni_dat['muni_pov'] = muni_dat["POOR_2012"]*100/muni_dat['muni_estpop']
muni_dat.describe()
Out[156]:
In [158]:
from bokeh.palettes import Reds9
#assign colors for each municipality using Bokeh's Reds9 color palette
muni_pov = [i for i in muni_dat['muni_pov']]
muni_color = []
for pov in muni_pov:
if np.isnan(pov)==True:
muni_color.append(Reds9[0])
else:
muni_color.append(Reds9[int(pov/100*8)])
Now that our data's clean and ready to go, let's plot it using Bokeh! To make the map more interactive, let's also add a pop-up balloon containing information on the municipalities using Bokeh's built-in Hover Tool.
(Hover your mouse on the map below (and try playing with the interactive tools again in the upper right hand corner of the map) to explore the map's functionality!)
In [164]:
from bokeh.plotting import ColumnDataSource
from bokeh.models import PanTool, BoxZoomTool, HoverTool, PreviewSaveTool, ResetTool, WheelZoomTool, BoxSelectTool
TOOLS2 = [PanTool(), BoxZoomTool(), BoxSelectTool(), HoverTool(), PreviewSaveTool(), ResetTool(), WheelZoomTool()]
p2 = figure(title="Municipal-level Map of the Philippines", tools = TOOLS2, plot_width=900, plot_height=800)
source = ColumnDataSource(data=dict(
x=muni_lat,
y=muni_lon,
color=muni_color,
name=muni_name,
rate=muni_pov,
))
hover = p2.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("Municipality / City", "@name"),
("Poverty Rate", "@rate%"),
("(Latitude, Longitude)", "($x, $y)")
]
p2.patches('x', 'y', source=source,
fill_color=muni_color, fill_alpha=0.7,
line_color="white", line_width=0.5)
output_file("sample_map_with_hover.html", title="Municipal-level Map of the Philippines")
show(p2)